﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using Adp.Pleiade.Fwk.Common;
using System.Collections;

namespace OutillageTests
{
    public static class GenerationXML
    {
        // StreamWriter d'écriture dans les fichier .xml
        private static StreamWriter sW;

        // Compteurs d'objets complexes
        private static int recordCounter;
        private static int recordListCounter;
        private static int genericListCounter;
        private static int dictionaryCounter;

        // Conteneurs de IList et IDictionary
        private static Dictionary<int, genericLists> GenericLists;
        private static Dictionary<int, dictionaries> Dicos;

        private static Dictionary<Type, Dictionary<MethodInfo, bool>> returnCarto;

        public static Dictionary<Type, Dictionary<MethodInfo, bool>> GenererXML(string path, 
            Dictionary<Type, Dictionary<MethodInfo, bool>> uneCarto)
        {
            returnCarto = new Dictionary<Type, Dictionary<MethodInfo, bool>>();

            if (!Directory.Exists(path)) Directory.CreateDirectory(path);
            
            foreach (KeyValuePair<Type, Dictionary<MethodInfo, bool>> elementCarto in uneCarto)
            {
                // Exclusion des interfaces
                if (!elementCarto.Key.IsInterface)
                {
                    recordCounter = 0;
                    recordListCounter = 0;
                    genericListCounter = 0;
                    dictionaryCounter = 0;
                    GenericLists = new Dictionary<int, genericLists>();
                    Dicos = new Dictionary<int, dictionaries>();

                    var dicoMethodInfo = new Dictionary<MethodInfo, bool>();

                    //var methodesAGenerer = elementCarto.Key.GetMethods();
                        //BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

                    InitialisationEnteteXML(path, elementCarto);

                    // Gestion des méthodes                    
                    foreach (KeyValuePair<MethodInfo, bool> element in elementCarto.Value)
                    {
                        if (element.Key != null)
                        {
                            bool implementationTest = true;

                            // Elligibilité de la méthode : présente dans la cartographie et identifiée comme testable
                            if (/*elementCarto.Value.ContainsKey(methodInfo) &&*/ element.Value)
                            {
                                // Traitement d'une méthode
                                InitialiserNoeudMethode(element.Key);

                                // Gestion des paramètres
                                GererParametresEtRetour(implementationTest, element.Key);

                                // Fin de méthode
                                FinaliserNoeudMethode(element.Key);
                            }
                            else
                                implementationTest = false;

                            // Alimentation de la carto locale des methodInfo
                            dicoMethodInfo.Add(element.Key, implementationTest);
                        }
                    }

                    // Gestion des objets complexes
                    GererObjetsComplexes();

                    FinaliserXML(elementCarto);

                    // Alimentation de la carto de retour
                    returnCarto.Add(elementCarto.Key, dicoMethodInfo);
                }
            }
            return returnCarto;
        }

        private static void FinaliserXML(KeyValuePair<Type, Dictionary<MethodInfo, bool>> elementCarto)
        {
            writeXML("</" + elementCarto.Key.Name + "TestData>", 1, true);
            sW.Close();
        }

        private static void GererObjetsComplexes()
        {
            writeXML("<ObjetsComplexes>", 2, true);

            // Gestion des Record. Construction des Record à vide
            for (int i = 0; i < recordCounter; i++)
            {
                writeXML("<Record Value = \"Record" + i.ToString() + "\">", 3, true);
                writeXML("<Field />", 4, true);
                writeXML("</Record>", 3, true);
            }

            // Gestion des RecordList. Construction des RecordList à vide
            for (int i = 0; i < recordListCounter; i++)
            {
                writeXML("<RecordList Value = \"RecordList" + i.ToString() + "\">", 3, true);
                writeXML("<Field />", 4, true);
                writeXML("</RecordList>", 3, true);
            }

            // Gestion des List<>. Construction des List à vide
            for (int i = 0; i < genericListCounter; i++)
            {
                writeXML("<LIST Value=\"LIST" +
                    i.ToString() + "\" Type=\"" +
                    GenericLists[i].listType.ToString() + "\">", 3, true);
                writeXML("<Attribute />", 4, true);
                writeXML("</LIST>", 3, true);
            }

            // Gestion des Dictionary<>. Construction des Dictionary à vide
            for (int i = 0; i < dictionaryCounter; i++)
            {
                writeXML("<DICTIONARY Value=\"DICTIONARY" +
                    i.ToString() + "\" KeyType=\"" +
                    Dicos[i].keyType.ToString() + "\" ValueType=\"" +
                    Dicos[i].valueType.ToString() + "\">", 3, true);
                writeXML("<KeyValuePairs Key=\"\" Value=\"\" />", 4, true);
                writeXML("</DICTIONARY>", 3, true);
            }

            writeXML("</ObjetsComplexes>", 2, true);
        }

        private static void FinaliserNoeudMethode(MethodInfo methodInfo)
        {
            sW.Write('\n');
            writeXML("</" + methodInfo.Name + "Test" + ">", 2, true);
        }

        private static void GererParametresEtRetour(bool implementationTest, MethodInfo methodInfo)
        {
            writeXML("<" + methodInfo.Name + "TestParameters ", 3, false);
            if (methodInfo.GetParameters().Count() > 0)
            {
                foreach (ParameterInfo parameter in methodInfo.GetParameters())
                {
                    writeXML(parameter.Name + "=", 0, false);
                    implementationTest = writeDefaultValueByType(Tools.GetNormalizedType(parameter.ParameterType));
                }
            }

            // Gestion du type de retour
            if (methodInfo.ReturnType != typeof(void))
            {
                writeXML("expected=", 0, false);
                implementationTest = writeDefaultValueByType(methodInfo.ReturnType);
            }

            writeXML("AutoTU=\"" + implementationTest.ToString() + "\" />", 0, false);
        }

        private static void InitialiserNoeudMethode(MethodInfo methodInfo)
        {
            writeXML("<" + methodInfo.Name + "Test" + ">", 2, true);
        }

        private static void InitialisationEnteteXML(string path, KeyValuePair<Type, Dictionary<MethodInfo, bool>> elementCarto)
        {
            sW = new StreamWriter(path + elementCarto.Key.Name + "TestData.xml");
            writeXML("<" + elementCarto.Key.Name + "TestData" + ">", 1, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="text">Texte à afficher</param>
        /// <param name="lvl">Niveau d'indentation</param>
        /// <param name="lineChange">si vrai, appelle un WriteLine au lieu d'un Write</param>
        private static void writeXML(string text, int lvl, bool lineChange)
        {
            if (lineChange) sW.WriteLine(indentation(lvl) + text);
            else sW.Write(indentation(lvl) + text);
        }

        private static bool writeDefaultValueByType(Type type)
        {
            var result = true;
            
            // Gestion des Record et RecordList
            if (typeof(Record).IsAssignableFrom(Tools.GetNormalizedType(type)))
            {
                sW.Write("\"Record" + recordCounter + "\" ");
                recordCounter++;
            }
            else if (typeof(RecordList).IsAssignableFrom(Tools.GetNormalizedType(type)))
            {
                sW.Write("\"RecordList" + recordListCounter + "\" ");
                recordListCounter++;
            }

            // Gestion des List<>
            else if (typeof(IList).IsAssignableFrom(Tools.GetNormalizedType(type)))
            {
                sW.Write("\" LIST" + genericListCounter + "\" ");
                GenericLists.Add(genericListCounter, new genericLists(Tools.GetNormalizedType(type).GetGenericArguments()[0]));
                genericListCounter++;
            } 

            // Gestion des Dictionnary<>
            else if (typeof(IDictionary).IsAssignableFrom(Tools.GetNormalizedType(type)))
            {
                sW.Write("\" DICTIONARY" + dictionaryCounter + "\" ");
                if (Tools.GetNormalizedType(type).GetGenericArguments().Count() > 0)
                {
                    Dicos.Add(dictionaryCounter, new dictionaries(Tools.GetNormalizedType(type).GetGenericArguments()[0], Tools.GetNormalizedType(type).GetGenericArguments()[1]));
                    dictionaryCounter++;
                }
                else
                {
                    sW.Write("\"Type non traité : " + type.Name + "\" ");
                    result = false;
                }

            }
            else
            {
                if (type.Name.Contains(typeof(string).Name)) sW.Write("\"\" ");
                else if (type.Name.Contains(typeof(int).Name)) sW.Write("\"0\" ");
                else if (type.Name.Contains(typeof(DateTime).Name)) sW.Write("\"30/12/1899\" ");
                else if (type.Name.Contains(typeof(double).Name)) sW.Write("\"0\" ");
                else if (type.Name.Contains(typeof(float).Name)) sW.Write("\"0\" ");
                else if (type.Name.Contains(typeof(bool).Name)) sW.Write("\"true\" ");

                else
                {
                    sW.Write("\"Type non traité : " + type.Name + "\" ");
                    result = false;
                }
            }
            return result;
        }

        private static string indentation(int lvl)
        {
            switch (lvl)
            {
                case 2:
                    return "  ";
                case 3:
                    return "      ";
                case 4:
                    return "            ";
                default:
                    return "";
            }
        }
    }

    internal class genericLists
    {
        public Type listType { get; set; }

        public genericLists(Type type) 
        {
            listType = type;
        }
    }

    internal class dictionaries
    {
        public Type keyType { get; set; }
        public Type valueType { get; set; }

        public dictionaries(Type keytype, Type valuetype)
        {
            keyType = keytype;
            valueType = valuetype;
        }
    }
}
